package defense;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.JPanel;

import defense.graphics.Explosion;
import defense.physics.Vec2f;

public class Game implements MouseMotionListener, MouseListener,
		ComponentListener, KeyListener {
	ArrayList<BallObject> enemies;
	ArrayList<Bullet> shots;
	ArrayList<Explosion> explosions;
	Vec2f mouse;
	JPanel panel;

	Vec2f cannonPosition;

	int weaponReload;
	private boolean mousePressed;

	Random nextEnemy;

	int lives;
	int score;
	int bombs;

	Font mainFont;
	private boolean pause;

	public Game(JPanel panel) {
		this.panel = panel;
		enemies = new ArrayList<>();
		shots = new ArrayList<>();
		explosions = new ArrayList<>();
		mouse = new Vec2f();
		cannonPosition = new Vec2f(panel.getWidth() / 2, panel.getHeight());
		panel.addMouseMotionListener(this);
		panel.addMouseListener(this);
		panel.addComponentListener(this);
		panel.addKeyListener(this);
		mousePressed = false;
		mainFont = new Font("Arial", Font.PLAIN, 24);

		nextEnemy = new Random(0x1234567890ABCDEFL);
		lives = 10;
		score = 0;
		pause = false;
		bombs = 5;
	}

	public void update() {

		if (lives <= 0 || pause) {
			return;
		}

		if (nextEnemy.nextFloat() < 0.01) {
			float r = 40.0f + nextEnemy.nextFloat() * 15.0f;
			if (nextEnemy.nextFloat() < 0.03)
				r = r + r;
			else if (nextEnemy.nextFloat() < 0.1)
				r = r + r;
			BallObject bo = new BallObject(new Vec2f(
					(float) (Math.random() * panel.getWidth()), -r), new Vec2f(
					(float) (Math.random() - 0.5) * 0, 0.9f), r);
			bo.setColor(new Color(nextEnemy.nextInt()));
			enemies.add(bo);
		}

		weaponReload--;

		for (int i = 0; i < explosions.size(); i++) {
			Explosion expl = explosions.get(i);
			expl.update();
			if (expl.isDead())
				explosions.remove(i--);
		}

		if (mousePressed) {
			fire();
		}

		for (int i = 0; i < enemies.size(); i++) {
			BallObject bo = enemies.get(i);
			for (int j = i + 1; j < enemies.size(); j++) {
				BallObject bo2 = enemies.get(j);
				bo.checkCollision(bo2);
			}
			for (int j = 0; j < shots.size(); j++) {
				BallObject bo2 = shots.get(j);
				boolean collided = bo.checkCollision(bo2);
				if (collided && bo2 instanceof Missile) {
					createExplosion((Missile) bo2);
					shots.remove(j--);
				}
			}
			bo.update();

			Vec2f pos = bo.getPos();
			float r = bo.getRadius();

			if (pos.x < r) {
				pos.x = r;
				bo.getVel().x = -bo.getVel().x;
			}
			if (pos.x > panel.getWidth() - r) {
				pos.x = panel.getWidth() - r;
				bo.getVel().x = -bo.getVel().x;
			}

			if (pos.y > panel.getHeight() + r) {
				lives--;
				enemies.remove(i--);
			} else if (pos.y < -r) {
				score++;
				enemies.remove(i--);
				if (nextEnemy.nextFloat() < 0.1f) {
					bombs++;
				}
			} else if (pos.x < -r || pos.x > panel.getWidth() + r || pos.y < -r
					|| pos.y > panel.getHeight() + r)
				enemies.remove(i--);
		}

		for (int i = 0; i < shots.size(); i++) {
			BallObject bo = shots.get(i);
			for (int j = i + 1; j < shots.size(); j++) {
				BallObject bo2 = shots.get(j);
				bo.checkCollision(bo2);
			}
			bo.update();

			Vec2f pos = bo.getPos();
			float r = bo.getRadius() + 50;
			if (pos.x < -r || pos.x > panel.getWidth() + r || pos.y < -r
					|| pos.y > panel.getHeight() + r)
				shots.remove(i--);
		}
	}

	private void createExplosion(Missile m) {
		float explosionVal = m.getExplosionVal();
		Vec2f expPos = m.getPos();

		explosions.add(new Explosion(expPos));

		for (int i = 0; i < enemies.size(); i++) {
			BallObject bo = enemies.get(i);
			Vec2f expToBo = bo.getPos().minus(expPos);
			addExplForce(expToBo, explosionVal, bo);
		}
		for (int i = 0; i < shots.size(); i++) {
			BallObject bo = shots.get(i);
			Vec2f expToBo = bo.getPos().minus(expPos);
			addExplForce(expToBo, explosionVal, bo);
		}
	}
	
	private void addExplForce(Vec2f expToBo, float explosionVal, BallObject bo) {
		float mag = expToBo.mag();
		expToBo.mult(explosionVal / (mag * mag * bo.getRadius()));
		bo.getVel().add(expToBo);
	}

	public void draw(Graphics2D g2d) {
		displayCannon(g2d);
		for (int i = 0; i < enemies.size(); i++) {
			BallObject bo = enemies.get(i);
			bo.display(g2d);
		}

		for (int i = 0; i < shots.size(); i++) {
			BallObject bo = shots.get(i);
			bo.display(g2d);
		}

		for (int i = 0; i < explosions.size(); i++) {
			Explosion expl = explosions.get(i);
			expl.display(g2d);
		}

		displayStats(g2d);

		if (lives <= 0) {
			g2d.setFont(mainFont);
			String text = "Game Over";
			g2d.drawChars(text.toCharArray(), 0, text.length(),
					panel.getWidth() / 2 - 50, panel.getHeight() / 2);
		}
	}

	void displayStats(Graphics2D g2d) {
		g2d.setFont(mainFont);
		String text = "Lives: " + lives;
		String scoreText = "Score: " + score;
		String bombsText = "Bombs: " + bombs;
		g2d.setColor(Color.black);
		g2d.drawChars(text.toCharArray(), 0, text.length(),
				panel.getWidth() - 180, 30);
		g2d.drawChars(scoreText.toCharArray(), 0, scoreText.length(),
				panel.getWidth() - 180, 60);
		g2d.drawChars(bombsText.toCharArray(), 0, bombsText.length(),
				panel.getWidth() - 180, 90);
	}

	void displayCannon(Graphics2D g2d) {
		Vec2f cannonToMouse = mouse.minus(cannonPosition);
		cannonToMouse.normalize();
		cannonToMouse.mult(40.0f);
		cannonToMouse.add(cannonPosition);
		g2d.setColor(Color.BLACK);
		g2d.drawLine((int) cannonPosition.x, (int) cannonPosition.y,
				(int) cannonToMouse.x, (int) cannonToMouse.y);
	}

	void fire() {
		final float bulletRadius = 6.0f;
		final float bulletSpeed = 8.0f;
		if (weaponReload > 0)
			return;
		Vec2f cannonToMouse = mouse.minus(cannonPosition);
		cannonToMouse.normalize();
		cannonToMouse.mult(bulletSpeed);
		Bullet bo = new Bullet(cannonPosition, cannonToMouse, bulletRadius);
		shots.add(bo);
		weaponReload = 6;
	}

	void fireBomb() {

		if (bombs <= 0)
			return;

		final float bulletRadius = 10.0f;
		final float bulletSpeed = 5.0f;

		Vec2f cannonToMouse = mouse.minus(cannonPosition);
		cannonToMouse.normalize();
		cannonToMouse.mult(bulletSpeed);
		Missile mo = new Missile(cannonPosition, cannonToMouse, bulletRadius);
		shots.add(mo);
		bombs--;
	}

	@Override
	public void mouseDragged(MouseEvent me) {
		mouse.x = me.getX();
		mouse.y = me.getY();
	}

	@Override
	public void mouseMoved(MouseEvent me) {
		mouse.x = me.getX();
		mouse.y = me.getY();
	}

	@Override
	public void mouseClicked(MouseEvent me) {
		mouse.x = me.getX();
		mouse.y = me.getY();
	}

	@Override
	public void mouseEntered(MouseEvent me) {
		mouse.x = me.getX();
		mouse.y = me.getY();
	}

	@Override
	public void mouseExited(MouseEvent me) {
		mouse.x = me.getX();
		mouse.y = me.getY();
	}

	@Override
	public void mousePressed(MouseEvent me) {
		mouse.x = me.getX();
		mouse.y = me.getY();
		if (me.getButton() == MouseEvent.BUTTON1)
			mousePressed = true;
		if (me.getButton() == MouseEvent.BUTTON3) {
			fireBomb();
		}
	}

	@Override
	public void mouseReleased(MouseEvent me) {
		mouse.x = me.getX();
		mouse.y = me.getY();
		if (me.getButton() == MouseEvent.BUTTON1)
			mousePressed = false;
	}

	@Override
	public void componentHidden(ComponentEvent arg0) {
		cannonPosition = new Vec2f(panel.getWidth() / 2, panel.getHeight());
	}

	@Override
	public void componentMoved(ComponentEvent arg0) {
		cannonPosition = new Vec2f(panel.getWidth() / 2, panel.getHeight());
	}

	@Override
	public void componentResized(ComponentEvent arg0) {
		cannonPosition = new Vec2f(panel.getWidth() / 2, panel.getHeight());
	}

	@Override
	public void componentShown(ComponentEvent arg0) {
		cannonPosition = new Vec2f(panel.getWidth() / 2, panel.getHeight());
	}

	@Override
	public String toString() {
		return "Game [enemies=" + enemies + ", shots=" + shots + ", mouse="
				+ mouse + ", panel=" + panel + ", cannonPosition="
				+ cannonPosition + ", weaponReload=" + weaponReload
				+ ", mousePressed=" + mousePressed + ", nextEnemy=" + nextEnemy
				+ ", lives=" + lives + ", score=" + score + ", mainFont="
				+ mainFont + "]";
	}

	@Override
	public void keyPressed(KeyEvent ke) {
		if (ke.getKeyCode() == KeyEvent.VK_P) {
			pause = !pause;
		}
	}

	@Override
	public void keyReleased(KeyEvent ke) {
		// TODO Auto-generated method stub

	}

	@Override
	public void keyTyped(KeyEvent ke) {
		// TODO Auto-generated method stub

	}
}
